package com.gitee.wsl.ext.string

import com.gitee.wsl.ext.number.HEX_RADIX
import com.gitee.wsl.ext.string.builder.escapeHTML

/**
 * Escapes special characters of a String for HTML output. Handles double quotes,
 * greater than, less than, ampersand, and characters greater than eight bit.
 */
fun String.escapeHTML(): String {
    return usingAppendable(Appendable::escapeHTML)
}

/**
 * Escaping special characters for encoding
 *
 * @param multiline
 * @return converted string with escaped special symbols
 */
 fun String.escapeSpecialCharacters(multiline: Boolean = false): String =
    if (multiline) {
        escapeControlChars(Char::isMultilineControlChar)
            .replace("\"\"\"", "\"\"\\\"")
            .escapeBackslashes("btnfruU\"\r\n")
    } else {
        escapeControlChars(Char::isControlChar)
            .replace("\"", "\\\"")
            .escapeBackslashes("btnfruU\"")
    }

/**
 * Checks if a character is an invalid control character for strings. Specifically,
 * this returns true for characters in Unicode block [`Cc`](https://www.compart.com/en/unicode/category/Cc)
 * excluding tab.
 *
 * Used to replace these characters with valid Unicode escapes when writing basic
 * strings, or throw an exception for them when writing literal strings.
 *
 * @return `true` if the character is a control character, except tab.
 */
internal fun Char.isControlChar() = this in CharCategory.CONTROL && this != '\t'

/**
 * Checks if a character is an invalid control character for multiline strings.
 * This is the same as [isControlChar], but excludes line terminators.
 *
 * @return `true` if the character is a control character, except tab and line
 * terminators.
 */
internal fun Char.isMultilineControlChar() = isControlChar() && this !in "\n\r"

private inline fun String.escapeControlChars(predicate: (Char) -> Boolean): String {
    val sb = StringBuilder(length)
    var last = 0
    for ((i, char) in withIndex()) {
        if (predicate(char)) {
            sb.append(this, last, i)
                .append(char.escapeControlChar())
            last = i + 1
        }
    }

    if (last < length) {
        sb.append(this, last, length)
    }

    return sb.toString()
}

private fun Char.escapeControlChar() = when (this) {
    '\t' -> "\\t"
    '\b' -> "\\b"
    '\n' -> "\\n"
    '\u000C' -> "\\f"
    '\r' -> "\\r"
    else -> {
        val hexDigits = code.toString(HEX_RADIX)

        "\\$SIMPLE_UNICODE_PREFIX${
            hexDigits.padStart(SIMPLE_UNICODE_LENGTH, '0')
        }"
    }
}

private fun String.escapeBackslashes(escapes: String): String {
    val sb = StringBuilder(length)
    var slashCount = 0
    var last = 0
    for ((i, char) in withIndex()) {
        if (char == '\\') {
            slashCount++
        } else {
            if (slashCount > 0 && char !in escapes && slashCount % 2 != 0) {
                sb.append(this, last, i - 1)
                    .append("\\\\$char")
                last = i + 1
            }

            slashCount = 0
        }
    }

    if (last < length) {
        sb.append(this, last, length)
    }

    return sb.toString()
}

private val winEscapeReg = """\^([\W\d])""".toRegex()

fun String.winEscape() = replace("^\\^\"", "\"").replace(winEscapeReg, "$1")
